package blackstar.agent.jvm;

import java.io.File;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;

import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import blackstar.agent.AgentConfig;
import blackstar.agent.jmx.MBeanUtils;
import blackstar.agent.service.ServiceFactory;

import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;

/**
 * @author raywu (ayufox@gmail.com)
 * 
 */
public class LocalJVM implements LocalJVMMBean, JMXProxy
{
	private final static Log LOGGER = LogFactory.getLog(LocalJVM.class);

	public final static String BEAN_TYPE = "LocalJVM";

	private boolean isStart;
	private String id;
	private String pid;
	private String displayName;
	private Map<String, Object> properties = new LinkedHashMap<String, Object>();

	private JMXConnector localVMConnector;
	private MBeanServerConnection localMBeanServerConnection;

	public LocalJVM(String pid, String displayName)
	{
		super();
		this.pid = pid;
		this.displayName = displayName;
	}

	public String getId()
	{
		return id;
	}

	public String getPid()
	{
		return pid;
	}

	public String getDisplayName()
	{
		return displayName;
	}

	public boolean isStart()
	{
		return this.isStart;
	}

	public JMXConnector getLocalVMConnector()
	{
		return localVMConnector;
	}

	public MBeanServerConnection getLocalMBeanServerConnection()
	{
		return localMBeanServerConnection;
	}

	public synchronized boolean start()
	{
		if (isStart)
		{
			LOGGER.info("LocalJVM[pid=" + this.pid + ",id=" + id
					+ "] had been started.");
			return true;
		}

		if (this.pid == null || this.pid.length() == 0)
		{
			LOGGER.info("Pid is needed for LocalJVM ");
			return false;
		}
		JMXServiceURL serviceURL = null;
		try
		{
			serviceURL = attachVM();
			LOGGER.info("Service URL [" + serviceURL + "]");
		} catch (Exception e)
		{
			LOGGER.error("Attach to LocalJVM[pid=" + this.pid + "] failed", e);
			return false;
		}

		try
		{
			localVMConnector = JMXConnectorFactory.connect(serviceURL);
			localMBeanServerConnection = localVMConnector
					.getMBeanServerConnection();

		} catch (Exception e)
		{
			LOGGER.error("Start Connection Failed[url=" + serviceURL
					+ "] failed", e);

			stop(true);

			return false;
		}

		loadAgent();

		initID();

		if (!MBeanUtils.registMBean(this.id, BEAN_TYPE, this))
		{

			stop(true);

			return false;
		}

		LOGGER.info("start success[" + this.id + "]");

		this.isStart = true;

		return true;
	}

	public synchronized boolean stop()
	{
		return stop(false);
	}

	public synchronized boolean stop(boolean force)
	{
		if (!force && !this.isStart)
		{
			LOGGER.info("LocalJVM[pid=" + this.pid + ",id=" + id
					+ "] had not been started yet.");
			return true;
		}

		MBeanUtils.unregistMBean(this.id, BEAN_TYPE);

		if (localVMConnector != null)
		{
			try
			{
				localVMConnector.close();
			} catch (Exception e)
			{
				LOGGER.error("Stop Connection failed", e);
			}
			localVMConnector = null;
			localMBeanServerConnection = null;
		}

		id = null;

		this.isStart = false;

		return false;
	}

	private void initID()
	{
		try
		{
			this.id = (String) this.localMBeanServerConnection.getAttribute(
					new ObjectName(MBeanUtils.ID_BEAN_NAME), "ID");

			LOGGER.info("getID[" + this.id + "] success");
		} catch (Exception e)
		{
			LOGGER.info("getID faild:" + e.getMessage());
			this.id = this.pid;
		}
	}

	private JMXServiceURL attachVM() throws AttachNotSupportedException,
			IOException, AgentLoadException, AgentInitializationException
	{
		VirtualMachine vm = VirtualMachine.attach(this.pid);

		String javaHome = vm.getSystemProperties().getProperty("java.home");
		String jmxAgent = javaHome + File.separator + "lib" + File.separator
				+ "management-agent.jar";

		vm.loadAgent(jmxAgent, "com.sun.management.jmxremote");

		Properties properties = vm.getAgentProperties();
		String address = (String) properties
				.get("com.sun.management.jmxremote.localConnectorAddress");

		vm.detach();

		return new JMXServiceURL(address);
	}

	private void loadAgent()
	{
		try
		{
			JVMNameResolver nameResolver = ServiceFactory.getJVMNameResolver();
			if (nameResolver != null)
			{
				String name = nameResolver.resolve(this);
				if (name != null)
				{
					String agents = AgentConfig.getProperty(name + ".agents");
					if (agents != null && agents.length() > 0)
					{
						String[] agentArray = agents.split(",");
						for (String agent : agentArray)
						{
							LOGGER.info("loadAgent[" + agent + "]");
							loadAgent(agent, AgentConfig.getProperty(name + "." + agent
									+ ".agentArgs"));
						}

						return;
					} else
					{
						LOGGER.info("No Agent for JVM[" + name + "]");
					}
				} else
				{
					LOGGER.info("Unknow JVM[" + this.getPid() + "]");
				}
			} else
			{
				LOGGER.info("No NameResolver Found");
			}
		} catch (Exception e)
		{
			LOGGER.info("error when loadAgent", e);
		}
	}

	public String loadAgent(String agentType, String agentArgs)
	{
		String agentLib = AgentConfig.getAgentLib(agentType);
		if (agentLib == null)
		{
			return "AgentType Not Exist";
		}
		LOGGER.info("loadAgent:agentLib=" + agentLib + ",agentArgs="
				+ agentArgs);

		try
		{
			VirtualMachine vm = VirtualMachine.attach(this.pid);

			vm.loadAgent(agentLib, agentArgs);

			vm.detach();

			return "Success";
		} catch (Exception e)
		{
			return "Error When Load Agent:" + e.getMessage();
		}
	}

	public Map<String, Object> getProperties()
	{
		return this.properties;
	}
}